package com.pactera.jep.service.sys.service.impl;

import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.pactera.jep.core.exception.ServiceException;
import com.pactera.jep.service.sys.conf.SyncProperties;
import com.pactera.jep.service.sys.conf.properties.Deptinfo;
import com.pactera.jep.service.sys.conf.properties.Employee;
import com.pactera.jep.service.sys.conf.properties.Token;
import com.pactera.jep.service.sys.service.SyncService;
import com.pactera.jep.service.sys.util.MD5Encoder;
import com.pactera.jep.sys.model.Staff;
import com.pactera.jep.sys.model.Unit;
import com.pactera.jep.sys.model.User;
import com.pactera.jep.sys.service.StaffService;
import com.pactera.jep.sys.service.UnitService;
import com.pactera.jep.sys.service.UserService;

/**
 * {@link SyncService}实现类，实现数据同步的相关功能
 * 
 * @author ghost
 *
 */
@Service
public class SyncServiceImpl implements SyncService {

	private final static Logger logger = LoggerFactory.getLogger(SyncServiceImpl.class);

	private final static String TOKEN_URL_TEMPLATE = "%s?grant_type=%s&client_id=%s&client_secret=%s&scope=%s";

	/**
	 * new一个restTemplate
	 */
	private RestTemplate restTemplate = new RestTemplate();

	@Autowired
	private UserService userService;
	
	@Autowired
	private StaffService staffService;
	
	@Autowired
	private UnitService unitService;

	@Resource
	private SyncProperties syncProperties;
	
	@Autowired
	private DataSourceTransactionManager txManager;
	
	// 缓存获取到的部门信息，因为方便同步员工数据时进行查询
	private List<JSONObject> deptCache;
	private List<Unit> unitCache;
	
	private String getToken() throws ServiceException {
		Token token = syncProperties.getToken();
		String url = token.getUrl();
		String clientId = token.getClientId();
		String clientSecret = token.getClientSecret();
		String grantType = token.getGrantType();
		String scope = token.getScope();
		if (StringUtils.isAnyEmpty(url, clientId, clientSecret, grantType, scope)) {
			logger.error("获取access_token相关配置为空！");
			throw new ServiceException("获取access_token相关配置为空！");
		}
		// 拼装url
		url = String.format(TOKEN_URL_TEMPLATE, url, grantType, clientId, clientSecret, scope);
		try {
			JSONObject result = restTemplate.getForEntity(url, JSONObject.class).getBody();
			if (result != null && result.containsKey("access_token")) {
				return result.getString("access_token");
			} else {
				logger.error("未能从接口中获取到access_token.");
				return null;
			}
		} catch (Exception e) {
			logger.error("请求{}时出现异常{}.", url, e.getMessage());
			return null;
		}
	}

	private void syncDeptInfo(String accessToken) throws ServiceException {
		if (StringUtils.isBlank(accessToken)) {
			logger.error("access_token为空!");
			throw new ServiceException("access_token为空！");
		}
		int pageIndex = 1;
		Deptinfo deptinfo = syncProperties.getDeptinfo();
		int pageSize = deptinfo.getPageSize();
		// 接口方默认一次最少查询100条
		if (pageSize < 100) {
			pageSize = 100;
		}
		// pageSize过大可能造成响应超时的异常...
		if (pageSize > 500) {
			pageSize = 500;
		}
		int effective = deptinfo.getEffective();
		effective = (effective == 0 || effective == 1) ? effective : -1;
		// 开始获取数据
		boolean stop = false;
		String baseUrl = deptinfo.getUrl();
		List<JSONObject> infos = new LinkedList<>();
		while (!stop) {
			String url = baseUrl + "?access_token=" + accessToken + "&pageindex=" + pageIndex + "&pagesize=" + pageSize;
			if (effective != -1) {
				// 拼装激活状态
				url += ("&is_effective=" + effective);
			}
			// 然后发起请求
			try {
				JSONArray result = fire(url);
				if (result != null && result.size() > 0) {
					for (int i = 0; i < result.size(); i++) {
						JSONObject item = result.getJSONObject(i);
						infos.add(item);
					}
					pageIndex++;
				} else {
					stop = true;
				}
			} catch (Exception e) {
				logger.error("请求{}出现异常:{}", url, e.getMessage());
				if (e instanceof ServiceException) {
					throw e;
				} else {
					throw new ServiceException(e);
				}
			}
		}
		// 目前需要添加两个数据，后续记得删除
		JSONObject pactera = new JSONObject();
		pactera.put("DEPARTMENT_ID", "PACTERA");
		pactera.put("DEPARTMENT_CODE", "10");
		pactera.put("DEPARTMENT_NAME", "文思海辉技术有限公司");
		pactera.put("COST_CENTER_CODE", "1000000000");
		pactera.put("IS_EFFECTIVE", "1");
		infos.add(pactera);
		JSONObject bg2 = new JSONObject();
		bg2.put("DEPARTMENT_ID", "BG2");
		bg2.put("DEPARTMENT_CODE", "1005");
		bg2.put("DEPARTMENT_NAME", "BG2");
		bg2.put("COST_CENTER_CODE", "2000000000");
		bg2.put("IS_EFFECTIVE", "1");
		infos.add(bg2);
		// 然后对deptInfos进行处理
		processDeptInfos(infos);
	}

	/**
	 * 处理部门数据
	 * 
	 * @param deptInfos
	 */
	private void processDeptInfos(List<JSONObject> deptInfos) {
		if (!deptInfos.isEmpty()) {
			logger.debug("开始处理数据...");
			// 首先去掉没激活的数据
			deptInfos = deptInfos.stream().filter(obj -> obj.getInteger("IS_EFFECTIVE") == 1).collect(Collectors.toList());
			deptInfos.sort((o1, o2) -> {
				int code1 = Integer.parseInt(o1.getString("DEPARTMENT_CODE"));
				int code2 = Integer.parseInt(o2.getString("DEPARTMENT_CODE"));
				return code1 < code2?-1:(code1 == code2?0:1);
			});
			// 对数据去重
			Set<String> codes = new HashSet<>();
			deptInfos = deptInfos.stream().filter(deptInfo -> codes.add(deptInfo.getString("DEPARTMENT_CODE"))).collect(Collectors.toList());
			logger.debug("对数据处理完成...");
			// 然后处理数据
			List<Unit> units = new ArrayList<Unit>();
			deptCache = deptInfos;
			for (int i = 0; i < deptInfos.size(); i++) {
//				DeptInfo deptInfo = deptInfos.get(i);
				JSONObject deptInfo = deptInfos.get(i);
				if (deptInfo.getInteger("IS_EFFECTIVE") == 0) {
					continue;
				} else {
					Unit unit = new Unit();
					String departmentCode = deptInfo.getString("DEPARTMENT_CODE");
					// 三个基础属性
					unit.setUnitCode(departmentCode);
					unit.setUnitName(deptInfo.getString("DEPARTMENT_NAME"));
					unit.setDisplayName(deptInfo.getString("DEPARTMENT_NAME"));
					JSONObject parent = null;
					for(int j = 0; j < i; j++) {
						JSONObject item = deptInfos.get(j);
						String dc = item.getString("DEPARTMENT_CODE");
						// 直接父级部门的编码的长度比子部门的大2
						if (departmentCode.startsWith(dc) && (dc.length() - departmentCode.length()) == -2) {
							parent = item;
							break;
						}
					}
					if (parent != null) {
						// 从units中查询到code是parent的deparentCode的unit数据
						Unit pu = null;
						for (Unit item : units) {
							if (item.getUnitCode().equals(parent.getString("DEPARTMENT_CODE"))) {
								pu = item;
								break;
							}
						}
						if (pu != null) {
							unit.setParentId(pu.getUnitCode());
							unit.setPathCode(pu.getPathCode() + "." + departmentCode + ".");
							unit.setPathName(pu.getPathName() + "/" + unit.getUnitName());
							unit.setHierarchy(Integer.parseInt(pu.getHierarchy()) + 20 + "");
							if (pu.getKind().equals("DEPT")) {
								pu.setKind("UNIT");
								pu.setOwner(pu.getUnitCode());
							}
							unit.setKind("DEPT");
							unit.setOwner(pu.getUnitCode());
							unit.setStatus("OPEN");
							units.add(unit);
						} 
					} else if (i == 0){
						// 父节点没有parentId
						unit.setOwner(departmentCode);
						unit.setPathCode("." + departmentCode + ".");
						unit.setPathName(unit.getUnitName());
						unit.setHierarchy("30");
						unit.setKind("UNIT");
						unit.setStatus("OPEN");
						units.add(unit);
					}
				}
			}
			unitCache = units;
			// 新建一个事务
			DefaultTransactionDefinition def = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
			TransactionStatus status = txManager.getTransaction(def);
			try {
				units.stream().forEach(unit -> {
					if (unitService.exists(unit.getUnitCode())) {
						unitService.update(unit);
					} else {
						unitService.insert(unit);
					}
				});
				// 保存完成提交事务
				txManager.commit(status);
			} catch (Exception e) {
				logger.error("更新部门数据时出现异常：{}", e.getMessage());
				e.printStackTrace();
				// 出现异常手动回滚事务
				txManager.rollback(status);
			}
		}
	}

	private void syncEmployeeInfo(String accessToken) throws ServiceException {
		logger.info("准备同步员工数据...");
		if (StringUtils.isBlank(accessToken)) {
			logger.error("access_token为空!");
			throw new ServiceException("access_token为空！");
		}
		int pageIndex = 1;
		Employee employee = syncProperties.getEmployee();
		String status = employee.getStatus();
		if (!("A".equals(status) || "I".equals(status))) {
			status = null;
		}
		int pageSize = employee.getPageSize();
		// 接口方默认一次最少查询100条
		if (pageSize < 100) {
			pageSize = 100;
		}
		// pageSize过大可能造成响应超时的异常...
		if (pageSize > 500) {
			pageSize = 500;
		}
		boolean stop = false;
		String baseUrl = employee.getUrl();
		List<JSONObject> items = new ArrayList<JSONObject>();
		while (!stop) {
			String url = baseUrl + "?access_token=" + accessToken + "&pageindex=" + pageIndex + "&pagesize=" + pageSize;
			// 如果没有明确指定是查询在职还是离职，那么将所有数据全部查询出来
			if (status != null) {
				url += ("&employee_status=" + status);
			}
			// 然后发起请求
			try {
				JSONArray result = fire(url);
				if (result != null && result.size() > 0) {
					for (int i = 0; i < result.size(); i++) {
						items.add(result.getJSONObject(i));
					}
					pageIndex++;
				} else {
					stop = true;
				}
			} catch (Exception e) {
				logger.error("请求{}出现异常:{}", url, e.getMessage());
				if (e instanceof ServiceException) {
					throw e;
				} else {
					throw new ServiceException(e);
				}
			}
		}
		processEmployee(items);
	}
	
	/**
	 * 处理员工数据，将员工数据同步至
	 * @param employeeInfos
	 */
	private void processEmployee(List<JSONObject> items) {
		if (items.isEmpty()) {
			logger.debug("获取到的员工数据为空！");
			return;
		} else {
			// 因为下面用并行流来处理数据，所以这里采用了ConcurrentLinkedDeque而不是链表来保存数据
			Deque<User> users = new ConcurrentLinkedDeque<>();
			Deque<Staff> staffs = new ConcurrentLinkedDeque<>();
			// 处理数据
			items.parallelStream().forEach(item -> {
				User user = new User();
				Staff staff = new Staff();
				// 员工姓名
				String employeeName = item.getString("EMPLOYEE_NAME");
				// 员工编号------->也就是工号
				String employeeNumber = item.getString("EMPLOYEE_NUMBER");
				// 手机号
				String mobile = item.getString("MOBILE_NUMBER");
				if (mobile != null && mobile.contains("null")) {
					// 应对手机号为"null"的情况
					mobile = null;
				}
				// 右键
				String email = item.getString("E_MAIL");
				// 部门编号
				// 注意，这个部门编号和unit中的unit_code不同，需要根据这个编号先去找部门编号
				String dc = item.getString("Department_ID");
				// 职位中文名
				String positionCNName = item.getString("POSITION_CN_NAME");
				// 员工状态，A代表在职，I代表离职
				String employeeStatus = item.getString("EMPLOYEE_STATUS");
				// 开始组装use和staff对象
				user.setUserId(employeeNumber);
				user.setUserName(employeeName);
				// 用户的初始密码设置为123456加上员工编号作为盐来进行加密
				user.setPassword(MD5Encoder.encode("123456", employeeNumber, 1));
				// 设置user的几个状态
				if (employeeStatus.equals("A")) {
					user.setAccountNonExpired(1);
					// 在用户表里面，non_locked表示账号未被锁定...
					user.setAccountNonLocked(0);
					user.setEnabled(1);
					user.setCredentialNonExpired(1);
				} else {
					user.setAccountNonExpired(0);
					user.setAccountNonLocked(1);
					user.setEnabled(0);
					user.setCredentialNonExpired(0);
				}
				staff.setStaffCode(employeeNumber);
				staff.setStaffName(employeeName);
				if (mobile != null) {
					staff.setMobile(mobile);
				}
				staff.setEmail(email);
				staff.setPositionName(positionCNName);
				staff.setStatus("A".equals(employeeStatus)?"ENABLE":"DISABLE");
				// TODO:不知道这里后续会进行什么改动，先把所有用户都修改成普通用户
				staff.setStaffType("COMMON");
				staff.setGender("MAN");
				// 然后填充部门和机构数据
				fillUnitAndDept(staff, dc);
				if (StringUtils.isNotBlank(staff.getUnitCode())) {
					// 没有的话说明部门数据不在表里面，那就不保存进表了
					users.add(user);
					staffs.add(staff);
				} 
			});
			// 手动启动事务
			DefaultTransactionDefinition def = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
			TransactionStatus status = txManager.getTransaction(def);
			try {
				staffs.stream().forEach(staff -> {
					// TODO:修改保存逻辑
					Staff resource = staffService.get(staff.getStaffCode());
					if (resource == null) {
						staffService.insert(staff);
					} else {
						// 什么用户需要更新？
						// 1. 用户的在职/离职状态进行了修改
						// 2. 用户的部门进行了修改(部门修改可能导致机构也修改)
						// 3. 用户的手机号进行了修改(邮箱是在创建用户的时候就已经创建了的，因此不会有修改这样一说)
						// 如果用户的在职状态、部门以及手机号都没有修改，那么判定为没有进行过修改，就不需要对staff和user进行
						/**
						 * 这里需要将现有数据和原有数据进行比较
						 * 1. 如果用户从在职改成离职状态，且离职前是管理员权限，那么将其权限从管理员修改成普通用户
						 * 2. 如果用户转换部门，且转部门之前是管理员权限，那么将其权限从管理员修改成普通用户
						 */
						String resDeptCode = resource.getDeptCode();
						String resDeptName = resource.getDeptName();
						String resStatus = resource.getStatus();
						String mobile = resource.getMobile();
						boolean needUpdate = false;
						// 首先判断用户的在职状态是否有改变
						if (!staff.getStatus().equals(resStatus)) {
							needUpdate = true;
						}
						// staffCode和staffName肯定不会为空的，为空只能说明数据有问题
						if (!staff.getDeptCode().equals(resDeptCode)) {
							// 部门编号修改才会被认为是部门修改，只是部门名称修改的话不认为是部门进行了修改
							if (!needUpdate) {
								needUpdate = true;
							}
						} else if (!staff.getDeptName().equals(resDeptName)) {
							if (!needUpdate) {
								// 如果在部门编号没有修改只是部门名称进行了修改，那么保留原来的级别
								needUpdate = true;
							}
							staff.setStaffType(resource.getStaffType());
						} else {
							// 如果既没有修改部门编号也没有修改部门名称，那么就不修改staffType
							staff.setStaffType(resource.getStaffType());
						}
						// 因为手机号可能为null，所以这里首先需要进行空判
						if (staff.getMobile() != null && !staff.getMobile().equals(mobile)) {
							if (!needUpdate) {
								needUpdate = true;
							}
						}
						// 如果上面的条件有一个进行了修改，那么执行修改操作，否则不进行任何处理
						if (needUpdate) {
							staffService.update(staff);
						}
					}
					if (!staffService.exists(staff.getStaffCode())) {
						staffService.insert(staff);
					} else {
						// 存在的话
						staffService.update(staff);
					}
				});
				users.stream().forEach(user -> {
					if (userService.exists(user.getUserId())) {
						userService.update(user);
					} else {
						userService.insert(user);
					}
				});
				txManager.commit(status);
			} catch (Exception e) {
				logger.error("保存用户数据过程中出现异常：{}", e.getMessage());
				e.printStackTrace();
				txManager.rollback(status);
			}
		}
	}
	
	private void fillUnitAndDept(Staff staff, String dc) {
		if (deptCache == null || deptCache.isEmpty()) {
			logger.debug("unitCache为空！");
			return;
		}
		Unit unit = getUnit(dc);
		// 首先遍历unitCache，找到满足条件的unit对象
		if (unit != null) {
			staff.setDeptCode(unit.getUnitCode());
			staff.setDeptName(unit.getUnitName());
			// 如果是机构的话那么unit和dept是相同的
			if ("UNIT".equalsIgnoreCase(unit.getKind())) {
				staff.setUnitCode(unit.getUnitCode());
				staff.setUnitName(unit.getUnitName());
			} else {
				// 根据owner再来找
				Unit parent = getCachedUnit(unit.getOwner()) ;
				if (parent != null) {
					staff.setUnitCode(parent.getUnitCode());
					staff.setUnitName(parent.getUnitName());
				}
			}
		}
	}
	
	/**
	 * 根据department_id找unit
	 * @param departId
	 * @return
	 */
	private Unit getUnit(String departId) {
		// 首先去找unitCode
		String unitCode = null;
		for(JSONObject obj : deptCache) {
			if (departId.equals(obj.getString("DEPARTMENT_ID"))) {
				unitCode = obj.getString("DEPARTMENT_CODE");
				break;
			}
		}
		if (StringUtils.isNotBlank(unitCode)) {
			return getCachedUnit(unitCode);
		} else {
			return null;
		}
	}
	
	private Unit getCachedUnit(String dc) {
		Unit unit = null;
		for (int i = 0; i < unitCache.size(); i++) {
			Unit item = unitCache.get(i);
			if (dc.equals(item.getUnitCode())) {
				unit = item;
				break;
			}
		}
		return unit;
	}

	/**
	 * 访问指定地址，并获取响应的数据
	 * @param url
	 * @return
	 * @throws ServiceException
	 */
	private JSONArray fire(String url) throws ServiceException {
		int retryCount = 3;
		Exception ex = null;
		while (--retryCount >= 0) {
			try {
				JSONArray result = restTemplate.getForEntity(url, JSONArray.class).getBody();
				return result;
			} catch (Exception e) {
				ex = e;
				continue;
			}
		}
		throw new ServiceException(ex);
	}

	@Override
	public void doSync() throws ServiceException {
		if (!syncProperties.isEnabled()) {
			logger.error("同步功能未启用！");
			throw new ServiceException("与ESB进行数据同步的功能未启用，请在配置文件中开启.");
		}
		String accessToken = getToken();
		logger.debug("获取到access_token : {}", accessToken);
		if (StringUtils.isNotBlank(accessToken)) {
			syncDeptInfo(accessToken);
			syncEmployeeInfo(accessToken);
			deptCache = null;
			unitCache = null;
		}
	}

}
